mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-09-24 23:32:48 +00:00
justify text
This commit is contained in:
@@ -318,6 +318,19 @@ public final class SWF implements SWFContainerItem, Timelined {
|
||||
return getCharacters().get(characterId);
|
||||
}
|
||||
|
||||
public FontTag getFont(int fontId) {
|
||||
CharacterTag characterTag = getCharacters().get(fontId);
|
||||
if (characterTag instanceof FontTag) {
|
||||
return (FontTag) characterTag;
|
||||
}
|
||||
|
||||
if (characterTag != null) {
|
||||
logger.log(Level.SEVERE, "CharacterTag should be a FontTag. characterId: {0}", fontId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ABCContainerTag> getAbcList() {
|
||||
if (abcList == null) {
|
||||
synchronized (this) {
|
||||
@@ -1550,7 +1563,6 @@ public final class SWF implements SWFContainerItem, Timelined {
|
||||
fos.write(Utf8Helper.getBytes(((FontTag) ch).toHtmlCanvas(1)));
|
||||
fos.write(Utf8Helper.getBytes("}\r\n\r\n"));
|
||||
} else {
|
||||
|
||||
if (ch instanceof ImageTag) {
|
||||
ImageTag image = (ImageTag) ch;
|
||||
String format = image.getImageFormat();
|
||||
@@ -2248,8 +2260,8 @@ public final class SWF implements SWFContainerItem, Timelined {
|
||||
if (!layer.isVisible) {
|
||||
continue;
|
||||
}
|
||||
CharacterTag character = timeline.swf.getCharacter(layer.characterId);
|
||||
|
||||
CharacterTag character = timeline.swf.getCharacter(layer.characterId);
|
||||
if (colorTransform == null) {
|
||||
colorTransform = new ColorTransform();
|
||||
}
|
||||
@@ -2398,6 +2410,7 @@ public final class SWF implements SWFContainerItem, Timelined {
|
||||
if (!layer.isVisible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CharacterTag character = timeline.swf.getCharacter(layer.characterId);
|
||||
Matrix mat = new Matrix(layer.matrix);
|
||||
mat = mat.preConcatenate(transformation);
|
||||
|
||||
@@ -435,11 +435,12 @@ public class FrameExporter {
|
||||
if (!layer.isVisible) {
|
||||
continue;
|
||||
}
|
||||
CharacterTag character = timeline.swf.getCharacter(layer.characterId);
|
||||
|
||||
CharacterTag character = timeline.swf.getCharacter(layer.characterId);
|
||||
if (colorTransform == null) {
|
||||
colorTransform = new ColorTransform();
|
||||
}
|
||||
|
||||
Matrix placeMatrix = new Matrix(layer.matrix);
|
||||
placeMatrix.scaleX /= unitDivisor;
|
||||
placeMatrix.scaleY /= unitDivisor;
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
@@ -503,14 +502,10 @@ public class DefineEditTextTag extends TextTag {
|
||||
try {
|
||||
fontId = Integer.parseInt(paramValue);
|
||||
|
||||
CharacterTag characterTag = swf.getCharacter(fontId);
|
||||
if (characterTag == null) {
|
||||
FontTag ft = swf.getFont(fontId);
|
||||
if (ft == null) {
|
||||
throw new TextParseException("Font not found.", lexer.yyline());
|
||||
}
|
||||
|
||||
if (!(characterTag instanceof FontTag)) {
|
||||
throw new TextParseException("Character tag is not a Font tag. CharacterID: " + fontId, lexer.yyline());
|
||||
}
|
||||
} catch (NumberFormatException ne) {
|
||||
throw new TextParseException("Invalid font value. Number expected. Found: " + paramValue, lexer.yyline());
|
||||
}
|
||||
@@ -641,6 +636,7 @@ public class DefineEditTextTag extends TextTag {
|
||||
hasFontClass = true;
|
||||
}
|
||||
this.autoSize = autoSize;
|
||||
this.align = align;
|
||||
if ((leftMargin > -1)
|
||||
|| (rightMargin > -1)
|
||||
|| (indent > -1)
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
@@ -220,16 +219,12 @@ public class DefineText2Tag extends TextTag {
|
||||
try {
|
||||
fontId = Integer.parseInt(paramValue);
|
||||
|
||||
CharacterTag characterTag = swf.getCharacter(fontId);
|
||||
if (characterTag == null) {
|
||||
FontTag ft = swf.getFont(fontId);
|
||||
if (ft == null) {
|
||||
throw new TextParseException("Font not found.", lexer.yyline());
|
||||
}
|
||||
|
||||
if (!(characterTag instanceof FontTag)) {
|
||||
throw new TextParseException("Character tag is not a Font tag. CharacterID: " + fontId, lexer.yyline());
|
||||
}
|
||||
|
||||
font = (FontTag) characterTag;
|
||||
font = (FontTag) ft;
|
||||
fontName = font.getSystemFontName();
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new TextParseException("Invalid font id - number expected. Found: " + paramValue, lexer.yyline());
|
||||
@@ -447,7 +442,7 @@ public class DefineText2Tag extends TextTag {
|
||||
|
||||
@Override
|
||||
public boolean alignText(TextAlign textAlign) {
|
||||
alignText(textRecords, textAlign);
|
||||
alignText(swf, textRecords, textAlign);
|
||||
setModified(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
@@ -226,16 +225,12 @@ public class DefineTextTag extends TextTag {
|
||||
try {
|
||||
fontId = Integer.parseInt(paramValue);
|
||||
|
||||
CharacterTag characterTag = swf.getCharacter(fontId);
|
||||
if (characterTag == null) {
|
||||
FontTag ft = swf.getFont(fontId);
|
||||
if (ft == null) {
|
||||
throw new TextParseException("Font not found.", lexer.yyline());
|
||||
}
|
||||
|
||||
if (!(characterTag instanceof FontTag)) {
|
||||
throw new TextParseException("Character tag is not a Font tag. CharacterID: " + fontId, lexer.yyline());
|
||||
}
|
||||
|
||||
font = (FontTag) characterTag;
|
||||
font = (FontTag) ft;
|
||||
fontName = font.getSystemFontName();
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new TextParseException("Invalid font id - number expected. Found: " + paramValue, lexer.yyline());
|
||||
@@ -455,7 +450,7 @@ public class DefineTextTag extends TextTag {
|
||||
|
||||
@Override
|
||||
public boolean alignText(TextAlign textAlign) {
|
||||
alignText(textRecords, textAlign);
|
||||
alignText(swf, textRecords, textAlign);
|
||||
setModified(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter;
|
||||
import com.jpexs.decompiler.flash.importers.TextImportResizeTextBoundsMode;
|
||||
import com.jpexs.decompiler.flash.tags.Tag;
|
||||
import com.jpexs.decompiler.flash.tags.text.TextAlign;
|
||||
import com.jpexs.decompiler.flash.tags.text.TextParseException;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
@@ -117,9 +116,15 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
}
|
||||
}
|
||||
|
||||
public static void alignText(List<TEXTRECORD> textRecords, TextAlign textAlign) {
|
||||
public static void alignText(SWF swf, List<TEXTRECORD> textRecords, TextAlign textAlign) {
|
||||
int xMin = Integer.MAX_VALUE;
|
||||
int maxWidth = 0;
|
||||
for (TEXTRECORD tr : textRecords) {
|
||||
int xOffset = tr.styleFlagsHasXOffset ? tr.xOffset : 0;
|
||||
if (xOffset < xMin) {
|
||||
xMin = xOffset;
|
||||
}
|
||||
|
||||
int width = tr.getTotalAdvance();
|
||||
|
||||
if (width > maxWidth) {
|
||||
@@ -127,30 +132,98 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
}
|
||||
}
|
||||
|
||||
FontTag font = null;
|
||||
|
||||
for (TEXTRECORD tr : textRecords) {
|
||||
if (tr.styleFlagsHasFont) {
|
||||
FontTag font2 = swf.getFont(tr.fontId);
|
||||
if (font2 != null) {
|
||||
font = font2;
|
||||
}
|
||||
}
|
||||
|
||||
/*if (tr.justified) {
|
||||
// Text record was aligned in Justify mode earier
|
||||
// restore the advances
|
||||
for (GLYPHENTRY ge : tr.glyphEntries) {
|
||||
char ch = font.glyphToChar(ge.glyphIndex);
|
||||
if (Character.isWhitespace(ch)) {
|
||||
ge.glyphAdvance = ge.originalAdvance;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
int width = tr.getTotalAdvance();
|
||||
switch (textAlign) {
|
||||
case LEFT:
|
||||
tr.xOffset = 0;
|
||||
tr.xOffset = xMin;
|
||||
tr.styleFlagsHasXOffset = true;
|
||||
break;
|
||||
case CENTER:
|
||||
tr.xOffset = (maxWidth - width) / 2;
|
||||
tr.xOffset = xMin + (maxWidth - width) / 2;
|
||||
tr.styleFlagsHasXOffset = true;
|
||||
break;
|
||||
case RIGHT:
|
||||
tr.xOffset = maxWidth - width;
|
||||
tr.xOffset = xMin + maxWidth - width;
|
||||
tr.styleFlagsHasXOffset = true;
|
||||
break;
|
||||
case JUSTIFY:
|
||||
tr.xOffset = 0;
|
||||
tr.xOffset = xMin;
|
||||
tr.styleFlagsHasXOffset = true;
|
||||
|
||||
if (font != null) {
|
||||
int diff = maxWidth - width;
|
||||
if (diff > 0) {
|
||||
int spaces = 0;
|
||||
int spaces2 = 0;
|
||||
int state = 0;
|
||||
List<GLYPHENTRY> glyphEntries = new ArrayList<>();
|
||||
List<GLYPHENTRY> glyphEntries2 = new ArrayList<>();
|
||||
for (GLYPHENTRY ge : tr.glyphEntries) {
|
||||
char ch = font.glyphToChar(ge.glyphIndex);
|
||||
boolean whitespace = Character.isWhitespace(ch);
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (!whitespace) {
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (whitespace) {
|
||||
spaces2++;
|
||||
glyphEntries2.add(ge);
|
||||
} else {
|
||||
spaces += spaces2;
|
||||
spaces2 = 0;
|
||||
glyphEntries.addAll(glyphEntries2);
|
||||
glyphEntries2.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spaces > 0) {
|
||||
int fix = diff / spaces;
|
||||
int remaining = diff - fix * spaces;
|
||||
for (GLYPHENTRY ge : glyphEntries) {
|
||||
int diff2 = fix;
|
||||
if (remaining-- > 0) {
|
||||
diff2++;
|
||||
}
|
||||
|
||||
//ge.originalAdvance = ge.glyphAdvance;
|
||||
ge.glyphAdvance += diff2;
|
||||
}
|
||||
|
||||
//tr.justified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> getTextRecordsAttributes(List<TEXTRECORD> list, List<Tag> tags) {
|
||||
public static Map<String, Object> getTextRecordsAttributes(List<TEXTRECORD> list, SWF swf) {
|
||||
Map<String, Object> att = new HashMap<>();
|
||||
RECT textBounds = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE);
|
||||
FontTag font = null;
|
||||
@@ -176,13 +249,9 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
for (int r = 0; r < list.size(); r++) {
|
||||
TEXTRECORD rec = list.get(r);
|
||||
if (rec.styleFlagsHasFont) {
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof FontTag) {
|
||||
FontTag ft = (FontTag) t;
|
||||
if (ft.getFontId() == rec.fontId) {
|
||||
font = ft;
|
||||
}
|
||||
}
|
||||
FontTag font2 = swf.getFont(rec.fontId);
|
||||
if (font2 != null) {
|
||||
font = font2;
|
||||
}
|
||||
textHeight = rec.textHeight;
|
||||
glyphs = font.getGlyphShapeTable();
|
||||
@@ -332,9 +401,9 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
}
|
||||
}
|
||||
if (rec.styleFlagsHasFont) {
|
||||
CharacterTag character = swf.getCharacter(rec.fontId);
|
||||
if (character instanceof FontTag) {
|
||||
font = (FontTag) character;
|
||||
FontTag font2 = swf.getFont(rec.fontId);
|
||||
if (font2 != null) {
|
||||
font = font2;
|
||||
}
|
||||
glyphs = font == null ? null : font.getGlyphShapeTable();
|
||||
textHeight = rec.textHeight;
|
||||
@@ -381,7 +450,7 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
ExportRectangle result = null;
|
||||
for (TEXTRECORD rec : textRecords) {
|
||||
if (rec.styleFlagsHasFont) {
|
||||
font = (FontTag) swf.getCharacter(rec.fontId);
|
||||
font = swf.getFont(rec.fontId);
|
||||
glyphs = font == null ? null : font.getGlyphShapeTable();
|
||||
textHeight = rec.textHeight;
|
||||
}
|
||||
@@ -472,7 +541,7 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
}
|
||||
}
|
||||
if (rec.styleFlagsHasFont) {
|
||||
font = (FontTag) swf.getCharacter(rec.fontId);
|
||||
font = swf.getFont(rec.fontId);
|
||||
fontId = rec.fontId;
|
||||
glyphs = font.getGlyphShapeTable();
|
||||
textHeight = rec.textHeight;
|
||||
@@ -518,7 +587,7 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
|
||||
}
|
||||
}
|
||||
if (rec.styleFlagsHasFont) {
|
||||
font = (FontTag) swf.getCharacter(rec.fontId);
|
||||
font = swf.getFont(rec.fontId);
|
||||
glyphs = font.getGlyphShapeTable();
|
||||
textHeight = rec.textHeight;
|
||||
}
|
||||
|
||||
@@ -1282,7 +1282,7 @@ public class XFLConverter {
|
||||
MATRIX matrix = rec.placeMatrix;
|
||||
String recCharStr;
|
||||
if (character instanceof TextTag) {
|
||||
recCharStr = convertText(null, tags, (TextTag) character, matrix, filters, null);
|
||||
recCharStr = convertText(null, (TextTag) character, matrix, filters, null);
|
||||
} else if (character instanceof DefineVideoStreamTag) {
|
||||
recCharStr = convertVideoInstance(null, matrix, (DefineVideoStreamTag) character, null);
|
||||
} else {
|
||||
@@ -1904,7 +1904,7 @@ public class XFLConverter {
|
||||
} else {
|
||||
shapeTween = false;
|
||||
if (character instanceof TextTag) {
|
||||
elements += convertText(instanceName, tags, (TextTag) character, matrix, filters, clipActions);
|
||||
elements += convertText(instanceName, (TextTag) character, matrix, filters, clipActions);
|
||||
} else if (character instanceof DefineVideoStreamTag) {
|
||||
elements += convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions);
|
||||
} else {
|
||||
@@ -2332,7 +2332,7 @@ public class XFLConverter {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static String convertText(String instanceName, List<Tag> tags, TextTag tag, MATRIX m, List<FILTER> filters, CLIPACTIONS clipActions) {
|
||||
public static String convertText(String instanceName, TextTag tag, MATRIX m, List<FILTER> filters, CLIPACTIONS clipActions) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
|
||||
if (m == null) {
|
||||
@@ -2349,7 +2349,8 @@ public class XFLConverter {
|
||||
filterStr += "</filters>";
|
||||
}
|
||||
|
||||
for (Tag t : tags) {
|
||||
SWF swf = tag.getSwf();
|
||||
for (Tag t : swf.tags) {
|
||||
if (t instanceof CSMTextSettingsTag) {
|
||||
CSMTextSettingsTag c = (CSMTextSettingsTag) t;
|
||||
if (c.textID == tag.getCharacterId()) {
|
||||
@@ -2358,6 +2359,7 @@ public class XFLConverter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String fontRenderingMode = "standard";
|
||||
String antiAlias = "";
|
||||
if (csmts != null) {
|
||||
@@ -2386,19 +2388,12 @@ public class XFLConverter {
|
||||
textRecords = ((DefineText2Tag) tag).textRecords;
|
||||
}
|
||||
|
||||
looprec:
|
||||
for (TEXTRECORD rec : textRecords) {
|
||||
if (rec.styleFlagsHasFont) {
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof FontTag) {
|
||||
FontTag ft = (FontTag) t;
|
||||
if (ft.getFontId() == rec.fontId) {
|
||||
if (ft.isSmall()) {
|
||||
fontRenderingMode = "bitmap";
|
||||
break looprec;
|
||||
}
|
||||
}
|
||||
}
|
||||
FontTag ft = swf.getFont(rec.fontId);
|
||||
if (ft != null && ft.isSmall()) {
|
||||
fontRenderingMode = "bitmap";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2412,7 +2407,7 @@ public class XFLConverter {
|
||||
ret.append(" instanceName=\"").append(xmlString(instanceName)).append("\"");
|
||||
}
|
||||
ret.append(antiAlias);
|
||||
Map<String, Object> attrs = TextTag.getTextRecordsAttributes(textRecords, tags);
|
||||
Map<String, Object> attrs = TextTag.getTextRecordsAttributes(textRecords, swf);
|
||||
|
||||
ret.append(" width=\"").append(tag.getBounds().getWidth() / 2).append("\" height=\"").append(tag.getBounds().getHeight()).append("\" autoExpand=\"true\" isSelectable=\"false\">");
|
||||
ret.append(matStr);
|
||||
@@ -2444,13 +2439,8 @@ public class XFLConverter {
|
||||
fontId = rec.fontId;
|
||||
fontName = null;
|
||||
textHeight = rec.textHeight;
|
||||
font = null;
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof FontTag) {
|
||||
if (((FontTag) t).getFontId() == fontId) {
|
||||
font = (FontTag) t;
|
||||
}
|
||||
}
|
||||
font = swf.getFont(fontId);
|
||||
for (Tag t : swf.tags) {
|
||||
if (t instanceof DefineFontNameTag) {
|
||||
if (((DefineFontNameTag) t).fontId == fontId) {
|
||||
fontName = ((DefineFontNameTag) t).fontName;
|
||||
@@ -2506,16 +2496,9 @@ public class XFLConverter {
|
||||
} else if (tag instanceof DefineEditTextTag) {
|
||||
DefineEditTextTag det = (DefineEditTextTag) tag;
|
||||
String tagName;
|
||||
for (Tag t : tags) {
|
||||
if (t instanceof FontTag) {
|
||||
FontTag ft = (FontTag) t;
|
||||
if (ft.getFontId() == det.fontId) {
|
||||
if (ft.isSmall()) {
|
||||
fontRenderingMode = "bitmap";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
FontTag ft = swf.getFont(det.fontId);
|
||||
if (ft != null && ft.isSmall()) {
|
||||
fontRenderingMode = "bitmap";
|
||||
}
|
||||
if (!det.useOutlines) {
|
||||
fontRenderingMode = "device";
|
||||
@@ -2579,7 +2562,7 @@ public class XFLConverter {
|
||||
}
|
||||
|
||||
if (det.html) {
|
||||
ret.append(convertHTMLText(tags, det, txt));
|
||||
ret.append(convertHTMLText(swf.tags, det, txt));
|
||||
} else {
|
||||
ret.append("<DOMTextRun>");
|
||||
ret.append("<characters>").append(xmlString(txt)).append("</characters>");
|
||||
@@ -2598,18 +2581,12 @@ public class XFLConverter {
|
||||
}
|
||||
if (det.hasFont) {
|
||||
String fontName = null;
|
||||
FontTag ft = null;
|
||||
for (Tag u : tags) {
|
||||
for (Tag u : swf.tags) {
|
||||
if (u instanceof DefineFontNameTag) {
|
||||
if (((DefineFontNameTag) u).fontId == det.fontId) {
|
||||
fontName = ((DefineFontNameTag) u).fontName;
|
||||
}
|
||||
}
|
||||
if (u instanceof FontTag) {
|
||||
if (((FontTag) u).getFontId() == det.fontId) {
|
||||
ft = (FontTag) u;
|
||||
}
|
||||
}
|
||||
if (fontName != null && ft != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user