From 9e5f45a7d84a8dd5536d4057b6135219c065d506 Mon Sep 17 00:00:00 2001 From: David Schweinsberg Date: Wed, 21 Feb 2007 12:30:48 +0000 Subject: New Type 2 Charstring handling classes. --- src/net/java/dev/typecast/t2/T2Interpreter.java | 835 ++++++++++++++++++++++++ src/net/java/dev/typecast/t2/T2Mnemonic.java | 86 +++ 2 files changed, 921 insertions(+) create mode 100644 src/net/java/dev/typecast/t2/T2Interpreter.java create mode 100644 src/net/java/dev/typecast/t2/T2Mnemonic.java diff --git a/src/net/java/dev/typecast/t2/T2Interpreter.java b/src/net/java/dev/typecast/t2/T2Interpreter.java new file mode 100644 index 0000000..9b4ce50 --- /dev/null +++ b/src/net/java/dev/typecast/t2/T2Interpreter.java @@ -0,0 +1,835 @@ +/* + * $Id: T2Interpreter.java,v 1.1 2007-02-21 12:30:48 davidsch Exp $ + * + * Typecast - The Font Development Environment + * + * Copyright (c) 2004-2007 David Schweinsberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.java.dev.typecast.t2; + +import java.util.ArrayList; + +import net.java.dev.typecast.ot.Point; + +import net.java.dev.typecast.ot.table.CharstringType2; + +/** + * Type 2 Charstring Interpreter. Operator descriptions are quoted from + * Adobe's Type 2 Charstring Format document -- 5117.Type2.pdf. + * @author David Schweinsberg + * @version $Id: T2Interpreter.java,v 1.1 2007-02-21 12:30:48 davidsch Exp $ + */ +public class T2Interpreter { + + private static final int ARGUMENT_STACK_LIMIT = 48; + private static final int SUBR_STACK_LIMIT = 10; + private static final int TRANSIENT_ARRAY_ELEMENT_COUNT = 32; + + private Number[] _argStack = new Number[ARGUMENT_STACK_LIMIT]; + private int _argStackIndex = 0; + private int[] _subrStack = new int[SUBR_STACK_LIMIT]; + private int _subrStackIndex = 0; + private Number[] _transientArray = new Number[TRANSIENT_ARRAY_ELEMENT_COUNT]; + + private ArrayList _points; + + /** Creates a new instance of T2Interpreter */ + public T2Interpreter() { + } + + /** + * Moves the current point to a position at the relative coordinates + * (dx1, dy1). + */ + private void _rmoveto() { + int dy1 = popArg().intValue(); + int dx1 = popArg().intValue(); + clearArg(); + Point lastPoint = getLastPoint(); + moveTo(lastPoint.x + dx1, lastPoint.y + dy1); + } + + /** + * Moves the current point dx1 units in the horizontal direction. + */ + private void _hmoveto() { + int dx1 = popArg().intValue(); + clearArg(); + Point lastPoint = getLastPoint(); + moveTo(lastPoint.x + dx1, lastPoint.y); + } + + /** + * Moves the current point dy1 units in the vertical direction. + */ + private void _vmoveto() { + int dy1 = popArg().intValue(); + clearArg(); + Point lastPoint = getLastPoint(); + moveTo(lastPoint.x, lastPoint.y + dy1); + } + + /** + * Appends a line from the current point to a position at the + * relative coordinates dxa, dya. Additional rlineto operations are + * performed for all subsequent argument pairs. The number of + * lines is determined from the number of arguments on the stack. + */ + private void _rlineto() { + int count = getArgCount() / 2; + for (int i = 0; i < count; ++i) { + int dy = popArg().intValue(); + int dx = popArg().intValue(); + Point lastPoint = getLastPoint(); + lineTo(lastPoint.x + dx, lastPoint.y + dy); + } + clearArg(); + } + + /** + * Appends a horizontal line of length dx1 to the current point. + * With an odd number of arguments, subsequent argument pairs + * are interpreted as alternating values of dy and dx, for which + * additional lineto operators draw alternating vertical and + * horizontal lines. With an even number of arguments, the + * arguments are interpreted as alternating horizontal and + * vertical lines. The number of lines is determined from the + * number of arguments on the stack. + */ + private void _hlineto() { + boolean oddCount = getArgCount() % 2 == 1; + int count = getArgCount() / 2; + if (oddCount) { + for (int i = 0; i < count; ++i) { + double dxb = popArg().doubleValue(); + double dya = popArg().doubleValue(); + } + double dx1 = popArg().doubleValue(); + } else { + for (int i = 0; i < count; ++i) { + double dyb = popArg().doubleValue(); + double dxa = popArg().doubleValue(); + } + } + clearArg(); + } + + /** + * Appends a vertical line of length dy1 to the current point. With + * an odd number of arguments, subsequent argument pairs are + * interpreted as alternating values of dx and dy, for which + * additional lineto operators draw alternating horizontal and + * vertical lines. With an even number of arguments, the + * arguments are interpreted as alternating vertical and + * horizontal lines. The number of lines is determined from the + * number of arguments on the stack. + */ + private void _vlineto() { + int count = getArgCount(); + Number[] nums = new Number[count]; + for (int i = 0; i < count; ++i) { + nums[count - i - 1] = popArg(); + } + for (int i = 0; i < count; ++i) { + Point lastPoint = getLastPoint(); + if (i % 2 == 0) { + lineTo(lastPoint.x, lastPoint.y + nums[i].intValue()); + } else { + lineTo(lastPoint.x + nums[i].intValue(), lastPoint.y); + } + } + +// boolean oddCount = getArgCount() % 2 == 1; +// int count = getArgCount() / 2; +// if (oddCount) { +// for (int i = 0; i < count; ++i) { +// double dyb = popArg().doubleValue(); +// double dxa = popArg().doubleValue(); +// } +// double dy1 = popArg().doubleValue(); +// } else { +// for (int i = 0; i < count; ++i) { +// double dxb = popArg().doubleValue(); +// double dya = popArg().doubleValue(); +// } +// } + clearArg(); + } + + /** + * Appends a Bézier curve, defined by dxa...dyc, to the current + * point. For each subsequent set of six arguments, an additional + * curve is appended to the current point. The number of curve + * segments is determined from the number of arguments on the + * number stack and is limited only by the size of the number + * stack. + */ + private void _rrcurveto() { + int count = getArgCount() / 6; + for (int i = 0; i < count; ++i) { + double dyc = popArg().doubleValue(); + double dxc = popArg().doubleValue(); + double dyb = popArg().doubleValue(); + double dxb = popArg().doubleValue(); + double dya = popArg().doubleValue(); + double dxa = popArg().doubleValue(); + } + clearArg(); + } + + /** + * Appends one or more Bézier curves, as described by the + * dxa...dxc set of arguments, to the current point. For each curve, + * if there are 4 arguments, the curve starts and ends horizontal. + * The first curve need not start horizontal (the odd argument + * case). Note the argument order for the odd argument case. + */ + private void _hhcurveto() { + + clearArg(); + } + + /** + * Appends one or more Bézier curves to the current point. The + * tangent for the first Bézier must be horizontal, and the second + * must be vertical (except as noted below). + * If there is a multiple of four arguments, the curve starts + * horizontal and ends vertical. Note that the curves alternate + * between start horizontal, end vertical, and start vertical, and + * end horizontal. The last curve (the odd argument case) need not + * end horizontal/vertical. + */ + private void _hvcurveto() { + + clearArg(); + } + + /** + * Is equivalent to one rrcurveto for each set of six arguments + * dxa...dyc, followed by exactly one rlineto using the dxd, dyd + * arguments. The number of curves is determined from the count + * on the argument stack. + */ + private void _rcurveline() { + + clearArg(); + } + + /** + * Is equivalent to one rlineto for each pair of arguments beyond + * the six arguments dxb...dyd needed for the one rrcurveto + * command. The number of lines is determined from the count of + * items on the argument stack. + */ + private void _rlinecurve() { + + clearArg(); + } + + /** + * Appends one or more Bézier curves to the current point, where + * the first tangent is vertical and the second tangent is horizontal. + * This command is the complement of hvcurveto; see the + * description of hvcurveto for more information. + */ + private void _vhcurveto() { + + clearArg(); + } + + /** + * Appends one or more curves to the current point. If the argument + * count is a multiple of four, the curve starts and ends vertical. If + * the argument count is odd, the first curve does not begin with a + * vertical tangent. + */ + private void _vvcurveto() { + + clearArg(); + } + + /** + * Causes two Bézier curves, as described by the arguments (as + * shown in Figure 2 below), to be rendered as a straight line when + * the flex depth is less than fd /100 device pixels, and as curved lines + * when the flex depth is greater than or equal to fd/100 device + * pixels. + */ + private void _flex() { + + clearArg(); + } + + /** + * Causes the two curves described by the arguments dx1...dx6 to + * be rendered as a straight line when the flex depth is less than + * 0.5 (that is, fd is 50) device pixels, and as curved lines when the + * flex depth is greater than or equal to 0.5 device pixels. + */ + private void _hflex() { + + clearArg(); + } + + /** + * Causes the two curves described by the arguments to be + * rendered as a straight line when the flex depth is less than 0.5 + * device pixels, and as curved lines when the flex depth is greater + * than or equal to 0.5 device pixels. + */ + private void _hflex1() { + + clearArg(); + } + + /** + * Causes the two curves described by the arguments to be + * rendered as a straight line when the flex depth is less than 0.5 + * device pixels, and as curved lines when the flex depth is greater + * than or equal to 0.5 device pixels. + */ + private void _flex1() { + + clearArg(); + } + + /** + * Finishes a charstring outline definition, and must be the + * last operator in a character’s outline. + */ + private void _endchar() { + endContour(); + clearArg(); + } + + private void _hstem() { + + clearArg(); + } + + private void _vstem() { + + clearArg(); + } + + private void _hstemhm() { + + clearArg(); + } + + private void _vstemhm() { + + clearArg(); + } + + private void _hintmask() { + + clearArg(); + } + + private void _cntrmask() { + + clearArg(); + } + + /** + * Returns the absolute value of num. + */ + private void _abs() { + double num = popArg().doubleValue(); + pushArg(Math.abs(num)); + } + + /** + * Returns the sum of the two numbers num1 and num2. + */ + private void _add() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg(num1 + num2); + } + + /** + * Returns the result of subtracting num2 from num1. + */ + private void _sub() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg(num1 - num2); + } + + /** + * Returns the quotient of num1 divided by num2. The result is + * undefined if overflow occurs and is zero for underflow. + */ + private void _div() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg(num1 / num2); + } + + /** + * Returns the negative of num. + */ + private void _neg() { + double num = popArg().doubleValue(); + pushArg(-num); + } + + /** + * Returns a pseudo random number num2 in the range (0,1], that + * is, greater than zero and less than or equal to one. + */ + private void _random() { + pushArg(1.0 - Math.random()); + } + + /** + * Returns the product of num1 and num2. If overflow occurs, the + * result is undefined, and zero is returned for underflow. + */ + private void _mul() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg(num1 * num2); + } + + /** + * Returns the square root of num. If num is negative, the result is + * undefined. + */ + private void _sqrt() { + double num = popArg().doubleValue(); + pushArg(Math.sqrt(num)); + } + + /** + * Removes the top element num from the Type 2 argument stack. + */ + private void _drop() { + popArg(); + } + + /** + * Exchanges the top two elements on the argument stack. + */ + private void _exch() { + Number num2 = popArg(); + Number num1 = popArg(); + pushArg(num2); + pushArg(num1); + } + + /** + * Retrieves the element i from the top of the argument stack and + * pushes a copy of that element onto that stack. If i is negative, + * the top element is copied. If i is greater than X, the operation is + * undefined. + */ + private void _index() { + int i = popArg().intValue(); + Number[] nums = new Number[i]; + for (int j = 0; j < i; ++j) { + nums[j] = popArg(); + } + for (int j = i - 1; j >= 0; --j) { + pushArg(nums[j]); + } + pushArg(nums[i]); + } + + /** + * Performs a circular shift of the elements num(N–1) ... num0 on + * the argument stack by the amount J. Positive J indicates upward + * motion of the stack; negative J indicates downward motion. + * The value N must be a non-negative integer, otherwise the + * operation is undefined. + */ + private void _roll() { + int j = popArg().intValue(); + int n = popArg().intValue(); + Number[] nums = new Number[n]; + for (int i = 0; i < n; ++i) { + nums[i] = popArg(); + } + for (int i = n - 1; i >= 0; --i) { + pushArg(nums[(n + i + j) % n]); + } + } + + /** + * Duplicates the top element on the argument stack. + */ + private void _dup() { + Number any = popArg(); + pushArg(any); + pushArg(any); + } + + /** + * Stores val into the transient array at the location given by i. + */ + private void _put() { + int i = popArg().intValue(); + Number val = popArg(); + _transientArray[i] = val; + } + + /** + * Retrieves the value stored in the transient array at the location + * given by i and pushes the value onto the argument stack. If get + * is executed prior to put for i during execution of the current + * charstring, the value returned is undefined. + */ + private void _get() { + int i = popArg().intValue(); + pushArg(_transientArray[i]); + } + + /** + * Puts a 1 on the stack if num1 and num2 are both non-zero, and + * puts a 0 on the stack if either argument is zero. + */ + private void _and() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg((num1!=0.0) && (num2!=0.0) ? 1 : 0); + } + + /** + * Puts a 1 on the stack if either num1 or num2 are non-zero, and + * puts a 0 on the stack if both arguments are zero. + */ + private void _or() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg((num1!=0.0) || (num2!=0.0) ? 1 : 0); + } + + /** + * Returns a 0 if num1 is non-zero; returns a 1 if num1 is zero. + */ + private void _not() { + double num1 = popArg().doubleValue(); + pushArg((num1!=0.0) ? 0 : 1); + } + + /** + * Puts a 1 on the stack if num1 equals num2, otherwise a 0 (zero) + * is put on the stack. + */ + private void _eq() { + double num2 = popArg().doubleValue(); + double num1 = popArg().doubleValue(); + pushArg(num1 == num2 ? 1 : 0); + } + + /** + * Leaves the value s1 on the stack if v1 ? v2, or leaves s2 on the + * stack if v1 > v2. The value of s1 and s2 is usually the biased + * number of a subroutine. + */ + private void _ifelse() { + double v2 = popArg().doubleValue(); + double v1 = popArg().doubleValue(); + Number s2 = popArg(); + Number s1 = popArg(); + pushArg(v1 <= v2 ? s1 : s2); + } + + /** + * Calls a charstring subroutine with index subr# (actually the subr + * number plus the subroutine bias number, as described in section + * 2.3) in the Subrs array. Each element of the Subrs array is a + * charstring encoded like any other charstring. Arguments + * pushed on the Type 2 argument stack prior to calling the + * subroutine, and results pushed on this stack by the subroutine, + * act according to the manner in which the subroutine is coded. + * Calling an undefined subr (gsubr) has undefined results. + */ + private void _callsubr() { + + } + + /** + * Operates in the same manner as callsubr except that it calls a + * global subroutine. + */ + private void _callgsubr() { + + } + + /** + * Returns from either a local or global charstring subroutine, and + * continues execution after the corresponding call(g)subr. + */ + private void _return() { + + } + + public Point[] execute(CharstringType2 cs) { + _points = new ArrayList(); + cs.resetIP(); + while (cs.moreBytes()) { + while (cs.isOperandAtIndex()) { + pushArg(cs.nextOperand()); + } + int operator = cs.nextByte(); + if (operator == 12) { + operator = cs.nextByte(); + + // Two-byte operators + switch (operator) { + case T2Mnemonic.AND: + _and(); + break; + case T2Mnemonic.OR: + _or(); + break; + case T2Mnemonic.NOT: + _not(); + break; + case T2Mnemonic.ABS: + _abs(); + break; + case T2Mnemonic.ADD: + _add(); + break; + case T2Mnemonic.SUB: + _sub(); + break; + case T2Mnemonic.DIV: + _div(); + break; + case T2Mnemonic.NEG: + _neg(); + break; + case T2Mnemonic.EQ: + _eq(); + break; + case T2Mnemonic.DROP: + _drop(); + break; + case T2Mnemonic.PUT: + _put(); + break; + case T2Mnemonic.GET: + _get(); + break; + case T2Mnemonic.IFELSE: + _ifelse(); + break; + case T2Mnemonic.RANDOM: + _random(); + break; + case T2Mnemonic.MUL: + _mul(); + break; + case T2Mnemonic.SQRT: + _sqrt(); + break; + case T2Mnemonic.DUP: + _dup(); + break; + case T2Mnemonic.EXCH: + _exch(); + break; + case T2Mnemonic.INDEX: + _index(); + break; + case T2Mnemonic.ROLL: + _roll(); + break; + case T2Mnemonic.HFLEX: + _hflex(); + break; + case T2Mnemonic.FLEX: + _flex(); + break; + case T2Mnemonic.HFLEX1: + _hflex1(); + break; + case T2Mnemonic.FLEX1: + _flex1(); + break; + default: + //throw new Exception(); + return null; + } + } else { + + // One-byte operators + switch (operator) { + case T2Mnemonic.HSTEM: + _hstem(); + break; + case T2Mnemonic.VSTEM: + _vstem(); + break; + case T2Mnemonic.VMOVETO: + _vmoveto(); + break; + case T2Mnemonic.RLINETO: + _rlineto(); + break; + case T2Mnemonic.HLINETO: + _hlineto(); + break; + case T2Mnemonic.VLINETO: + _vlineto(); + break; + case T2Mnemonic.RRCURVETO: + _rrcurveto(); + break; + case T2Mnemonic.CALLSUBR: + _callsubr(); + break; + case T2Mnemonic.RETURN: + _return(); + break; + case T2Mnemonic.ENDCHAR: + _endchar(); + break; + case T2Mnemonic.HSTEMHM: + _hstemhm(); + break; + case T2Mnemonic.HINTMASK: + _hintmask(); + break; + case T2Mnemonic.CNTRMASK: + _cntrmask(); + break; + case T2Mnemonic.RMOVETO: + _rmoveto(); + break; + case T2Mnemonic.HMOVETO: + _hmoveto(); + break; + case T2Mnemonic.VSTEMHM: + _vstemhm(); + break; + case T2Mnemonic.RCURVELINE: + _rcurveline(); + break; + case T2Mnemonic.RLINECURVE: + _rlinecurve(); + break; + case T2Mnemonic.VVCURVETO: + _vvcurveto(); + break; + case T2Mnemonic.HHCURVETO: + _hhcurveto(); + break; + case T2Mnemonic.CALLGSUBR: + _callgsubr(); + break; + case T2Mnemonic.VHCURVETO: + _vhcurveto(); + break; + case T2Mnemonic.HVCURVETO: + _hvcurveto(); + break; + default: + //throw new Exception(); + return null; + } + } + } + Point[] pointArray = new Point[_points.size()]; + _points.toArray(pointArray); + return pointArray; + } + + /** + * The number of arguments on the argument stack + */ + private int getArgCount() { + return _argStackIndex; + } + + /** + * Pop a value off the argument stack + */ + private Number popArg() { + return _argStack[--_argStackIndex]; + } + + /** + * Push a value on to the argument stack + */ + private void pushArg(Number n) { + _argStack[_argStackIndex++] = n; + } + + /** + * Pop a value off the subroutine stack + */ + private int popSubr() { + return _subrStack[--_subrStackIndex]; + } + + /** + * Push a value on to the subroutine stack + */ + private void pushSubr(int n) { + _subrStack[_subrStackIndex++] = n; + } + + /** + * Clear the argument stack + */ + private void clearArg() { + _argStackIndex = 0; + } + + private Point getLastPoint() { + int size = _points.size(); + if (size > 0) { + return _points.get(size - 1); + } else { + return new Point(0, 0, true, false); + } + } + + private void moveTo(int x, int y) { + endContour(); + _points.add(new Point(x, y, true, false)); + } + + private void lineTo(int x, int y) { + _points.add(new Point(x, y, true, false)); + } + + private void curveTo(int dcx1, int dcy1, int dcx2, int dcy2, int dx, int dy) { + Point lastPoint = getLastPoint(); + int x = lastPoint.x + dcx1; + int y = lastPoint.y + dcy1; + _points.add(new Point(x, y, false, false)); + x += dcx2; + y += dcy2; + _points.add(new Point(x, y, false, false)); + x += dx; + y += dy; + _points.add(new Point(x, y, true, false)); + } + + private void endContour() { + Point lastPoint = getLastPoint(); + if (lastPoint != null) { + lastPoint.endOfContour = true; + } + } +} diff --git a/src/net/java/dev/typecast/t2/T2Mnemonic.java b/src/net/java/dev/typecast/t2/T2Mnemonic.java new file mode 100644 index 0000000..810aea1 --- /dev/null +++ b/src/net/java/dev/typecast/t2/T2Mnemonic.java @@ -0,0 +1,86 @@ +/* + * $Id: T2Mnemonic.java,v 1.1 2007-02-21 12:30:48 davidsch Exp $ + * + * Typecast - The Font Development Environment + * + * Copyright (c) 2004-2007 David Schweinsberg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.java.dev.typecast.t2; + +/** + * The Mnemonic representations of the Type 2 charstring instruction set. + * @author David Schweinsberg + * @version $Id: T2Mnemonic.java,v 1.1 2007-02-21 12:30:48 davidsch Exp $ + */ +public class T2Mnemonic { + + /** + * One byte operators + */ + public static final short HSTEM = 0x01; + public static final short VSTEM = 0x03; + public static final short VMOVETO = 0x04; + public static final short RLINETO = 0x05; + public static final short HLINETO = 0x06; + public static final short VLINETO = 0x07; + public static final short RRCURVETO = 0x08; + public static final short CALLSUBR = 0x0a; + public static final short RETURN = 0x0b; + public static final short ESCAPE = 0x0c; + public static final short ENDCHAR = 0x0e; + public static final short HSTEMHM = 0x12; + public static final short HINTMASK = 0x13; + public static final short CNTRMASK = 0x14; + public static final short RMOVETO = 0x15; + public static final short HMOVETO = 0x16; + public static final short VSTEMHM = 0x17; + public static final short RCURVELINE = 0x18; + public static final short RLINECURVE = 0x19; + public static final short VVCURVETO = 0x1a; + public static final short HHCURVETO = 0x1b; + public static final short CALLGSUBR = 0x1d; + public static final short VHCURVETO = 0x1e; + public static final short HVCURVETO = 0x1f; + + /** + * Two byte operators + */ + public static final short DOTSECTION = 0x00; + public static final short AND = 0x03; + public static final short OR = 0x04; + public static final short NOT = 0x05; + public static final short ABS = 0x09; + public static final short ADD = 0x0a; + public static final short SUB = 0x0b; + public static final short DIV = 0x0c; + public static final short NEG = 0x0e; + public static final short EQ = 0x0f; + public static final short DROP = 0x12; + public static final short PUT = 0x14; + public static final short GET = 0x15; + public static final short IFELSE = 0x16; + public static final short RANDOM = 0x17; + public static final short MUL = 0x18; + public static final short SQRT = 0x1a; + public static final short DUP = 0x1b; + public static final short EXCH = 0x1c; + public static final short INDEX = 0x1d; + public static final short ROLL = 0x1e; + public static final short HFLEX = 0x22; + public static final short FLEX = 0x23; + public static final short HFLEX1 = 0x24; + public static final short FLEX1 = 0x25; +} -- cgit v1.2.3