def buildPaintRadialGradient( self, colorLine: _ColorLineInput, c0: _PointTuple, c1: _PointTuple, r0: _ScalarInput, r1: _ScalarInput, ) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintRadialGradient) ot_paint.ColorLine = _to_color_line(colorLine) # normalize input types (which may or may not specify a varIdx) x0, y0 = _to_variable_value(c0[0]), _to_variable_value(c0[1]) r0 = _to_variable_value(r0) x1, y1 = _to_variable_value(c1[0]), _to_variable_value(c1[1]) r1 = _to_variable_value(r1) # avoid abrupt change after rounding when c0 is near c1's perimeter c = round_start_circle_stable_containment( (x0.value, y0.value), r0.value, (x1.value, y1.value), r1.value) x0, y0 = x0._replace(value=c.centre[0]), y0._replace(value=c.centre[1]) r0 = r0._replace(value=c.radius) for i, (x, y, r) in enumerate(((x0, y0, r0), (x1, y1, r1))): # rounding happens here as floats are converted to integers setattr(ot_paint, f"x{i}", _to_variable_int16(x)) setattr(ot_paint, f"y{i}", _to_variable_int16(y)) setattr(ot_paint, f"r{i}", _to_variable_uint16(r)) return ot_paint
def buildSolidColorPaint( paletteIndex: int, transparency: _ScalarInput = _DEFAULT_TRANSPARENCY) -> ot.Paint: self = ot.Paint() self.Format = 1 self.Color = buildColor(paletteIndex, transparency) return self
def buildPaintSolid(self, paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintSolid) ot_paint.Color = buildColorIndex(paletteIndex, alpha) return ot_paint
def buildRadialGradientPaint( colorLine: _ColorLineInput, c0: _PointInput, c1: _PointInput, r0: _ScalarInput, r1: _ScalarInput, affine: Optional[_AffineInput] = None, ) -> ot.Paint: self = ot.Paint() self.Format = 3 self.ColorLine = _to_color_line(colorLine) for i, pt in [(0, c0), (1, c1)]: setattr(self, f"c{i}", _to_variable_point(pt)) for i, r in [(0, r0), (1, r1)]: # distances are encoded as UShort so we round to int setattr(self, f"r{i}", _to_variable_int(r)) if affine is not None and not isinstance(affine, ot.Affine2x2): affine = buildAffine2x2(*affine) self.Affine = affine return self
def buildPaintTranslate(self, paint: _PaintInput, dx: _ScalarInput, dy: _ScalarInput): ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintTranslate) ot_paint.Paint = self.buildPaint(paint) ot_paint.dx = _to_variable_f16dot16_float(dx) ot_paint.dy = _to_variable_f16dot16_float(dy) return ot_paint
def buildPaintTransform(self, transform: _AffineInput, paint: _PaintInput) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintTransform) if not isinstance(transform, ot.Affine2x3): transform = buildAffine2x3(transform) ot_paint.Transform = transform ot_paint.Paint = self.buildPaint(paint) return ot_paint
def buildColrLayers(self, paints: List[_PaintInput]) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintColrLayers) self.slices.append(ot_paint) paints = [ self.buildPaint(p) for p in _build_n_ary_tree(paints, n=MAX_PAINT_COLR_LAYER_COUNT) ] # Look for reuse, with preference to longer sequences found_reuse = True while found_reuse: found_reuse = False ranges = sorted( _reuse_ranges(len(paints)), key=lambda t: (t[1] - t[0], t[1], t[0]), reverse=True, ) for lbound, ubound in ranges: reuse_lbound = self.reusePool.get( self._as_tuple(paints[lbound:ubound]), -1) if reuse_lbound == -1: continue new_slice = ot.Paint() new_slice.Format = int(ot.Paint.Format.PaintColrLayers) new_slice.NumLayers = ubound - lbound new_slice.FirstLayerIndex = reuse_lbound paints = paints[:lbound] + [new_slice] + paints[ubound:] found_reuse = True break ot_paint.NumLayers = len(paints) ot_paint.FirstLayerIndex = len(self.layers) self.layers.extend(paints) # Register our parts for reuse for lbound, ubound in _reuse_ranges(len(paints)): self.reusePool[self._as_tuple( paints[lbound:ubound])] = (lbound + ot_paint.FirstLayerIndex) return ot_paint
def _beforeBuildPaintColrLayers(self, dest, source): # Sketchy gymnastics: a sequence input will have dropped it's layers # into NumLayers; get it back if isinstance(source.get("NumLayers", None), collections.abc.Sequence): layers = source["NumLayers"] else: layers = source["Layers"] # Convert maps seqs or whatever into typed objects layers = [self.buildPaint(l) for l in layers] # No reason to have a colr layers with just one entry if len(layers) == 1: return layers[0], {} if self.cache is not None: # Look for reuse, with preference to longer sequences # This may make the layer list smaller layers = self.cache.try_reuse(layers) # The layer list is now final; if it's too big we need to tree it is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT layers = build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT) # We now have a tree of sequences with Paint leaves. # Convert the sequences into PaintColrLayers. def listToColrLayers(layer): if isinstance(layer, collections.abc.Sequence): return self.buildPaint( { "Format": ot.PaintFormat.PaintColrLayers, "Layers": [listToColrLayers(l) for l in layer], } ) return layer layers = [listToColrLayers(l) for l in layers] # No reason to have a colr layers with just one entry if len(layers) == 1: return layers[0], {} paint = ot.Paint() paint.Format = int(ot.PaintFormat.PaintColrLayers) paint.NumLayers = len(layers) paint.FirstLayerIndex = len(self.layers) self.layers.extend(layers) # Register our parts for reuse provided we aren't a tree # If we are a tree the leaves registered for reuse and that will suffice if self.cache is not None and not is_tree: self.cache.add(layers, paint.FirstLayerIndex) # we've fully built dest; empty source prevents generalized build from kicking in return paint, {}
def listToColrLayers(paint): if isinstance(paint, list): layers = [listToColrLayers(l) for l in paint] paint = ot.Paint() paint.Format = int(ot.PaintFormat.PaintColrLayers) paint.NumLayers = len(layers) paint.FirstLayerIndex = len(self.layers) self.layers.extend(layers) if self.layerReuseCache is not None: self.layerReuseCache.add(layers, paint.FirstLayerIndex) return paint
def buildPaintComposite( self, mode: _CompositeInput, source: _PaintInput, backdrop: _PaintInput, ): ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintComposite) ot_paint.SourcePaint = self.buildPaint(source) ot_paint.CompositeMode = _to_composite_mode(mode) ot_paint.BackdropPaint = self.buildPaint(backdrop) return ot_paint
def buildPaintRotate( self, paint: _PaintInput, angle: _ScalarInput, centerX: _ScalarInput, centerY: _ScalarInput, ) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintRotate) ot_paint.Paint = self.buildPaint(paint) ot_paint.angle = _to_variable_f16dot16_float(angle) ot_paint.centerX = _to_variable_f16dot16_float(centerX) ot_paint.centerY = _to_variable_f16dot16_float(centerY) return ot_paint
def buildLinearGradientPaint( colorLine: _ColorLineInput, p0: _PointInput, p1: _PointInput, p2: Optional[_PointInput] = None, ) -> ot.Paint: self = ot.Paint() self.Format = 2 self.ColorLine = _to_color_line(colorLine) if p2 is None: p2 = copy.copy(p1) for i, pt in enumerate((p0, p1, p2)): setattr(self, f"p{i}", _to_variable_point(pt)) return self
def buildPaintSweepGradient( self, colorLine: _ColorLineInput, centerX: _ScalarInput, centerY: _ScalarInput, startAngle: _ScalarInput, endAngle: _ScalarInput, ) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.PaintFormat.PaintSweepGradient) ot_paint.ColorLine = _to_color_line(colorLine) ot_paint.centerX = _to_variable_int16(centerX) ot_paint.centerY = _to_variable_int16(centerY) ot_paint.startAngle = _to_variable_f16dot16_float(startAngle) ot_paint.endAngle = _to_variable_f16dot16_float(endAngle) return ot_paint
def buildPaintSkew( self, paint: _PaintInput, xSkewAngle: _ScalarInput, ySkewAngle: _ScalarInput, centerX: _ScalarInput, centerY: _ScalarInput, ) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintSkew) ot_paint.Paint = self.buildPaint(paint) ot_paint.xSkewAngle = _to_variable_f16dot16_float(xSkewAngle) ot_paint.ySkewAngle = _to_variable_f16dot16_float(ySkewAngle) ot_paint.centerX = _to_variable_f16dot16_float(centerX) ot_paint.centerY = _to_variable_f16dot16_float(centerY) return ot_paint
def buildPaintLinearGradient( self, colorLine: _ColorLineInput, p0: _PointTuple, p1: _PointTuple, p2: Optional[_PointTuple] = None, ) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintLinearGradient) ot_paint.ColorLine = _to_color_line(colorLine) if p2 is None: p2 = copy.copy(p1) for i, (x, y) in enumerate((p0, p1, p2)): setattr(ot_paint, f"x{i}", _to_variable_int16(x)) setattr(ot_paint, f"y{i}", _to_variable_int16(y)) return ot_paint
def buildPaintRadialGradient( self, colorLine: _ColorLineInput, c0: _PointTuple, c1: _PointTuple, r0: _ScalarInput, r1: _ScalarInput, ) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintRadialGradient) ot_paint.ColorLine = _to_color_line(colorLine) for i, (x, y), r in [(0, c0, r0), (1, c1, r1)]: setattr(ot_paint, f"x{i}", _to_variable_int16(x)) setattr(ot_paint, f"y{i}", _to_variable_int16(y)) setattr(ot_paint, f"r{i}", _to_variable_uint16(r)) return ot_paint
def buildRadialGradientPaint( colorLine: _ColorLineInput, c0: _PointTuple, c1: _PointTuple, r0: _ScalarInput, r1: _ScalarInput, transform: Optional[_AffineInput] = None, ) -> ot.Paint: self = ot.Paint() self.Format = 3 self.ColorLine = _to_color_line(colorLine) for i, (x, y), r in [(0, c0, r0), (1, c1, r1)]: setattr(self, f"x{i}", _to_variable_int(x)) setattr(self, f"y{i}", _to_variable_int(y)) setattr(self, f"r{i}", _to_variable_int(r)) if transform is not None and not isinstance(transform, ot.Affine2x2): transform = buildAffine2x2(*transform) self.Transform = transform return self
def try_reuse(self, layers: List[ot.Paint]) -> List[ot.Paint]: found_reuse = True while found_reuse: found_reuse = False ranges = sorted( _reuse_ranges(len(layers)), key=lambda t: (t[1] - t[0], t[1], t[0]), reverse=True, ) for lbound, ubound in ranges: reuse_lbound = self.reusePool.get( self._as_tuple(layers[lbound:ubound]), -1 ) if reuse_lbound == -1: continue new_slice = ot.Paint() new_slice.Format = int(ot.PaintFormat.PaintColrLayers) new_slice.NumLayers = ubound - lbound new_slice.FirstLayerIndex = reuse_lbound layers = layers[:lbound] + [new_slice] + layers[ubound:] found_reuse = True break return layers
def buildSolidColorPaint(paletteIndex: int, alpha: _ScalarInput = _DEFAULT_ALPHA) -> ot.Paint: self = ot.Paint() self.Format = 1 self.Color = buildColorIndex(paletteIndex, alpha) return self
def _defaultPaintSolid(): paint = ot.Paint() paint.Alpha = _DEFAULT_ALPHA return paint
def buildPaintColrGlyph(self, glyph: str) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintColrGlyph) ot_paint.Glyph = glyph return ot_paint
def buildPaintGlyph(self, glyph: str, paint: _PaintInput) -> ot.Paint: ot_paint = ot.Paint() ot_paint.Format = int(ot.Paint.Format.PaintGlyph) ot_paint.Glyph = glyph ot_paint.Paint = self.buildPaint(paint) return ot_paint
def _beforeBuildPaintColrLayers(self, dest, source): # Sketchy gymnastics: a sequence input will have dropped it's layers # into NumLayers; get it back if isinstance(source.get("NumLayers", None), collections.abc.Sequence): layers = source["NumLayers"] else: layers = source["Layers"] # Convert maps seqs or whatever into typed objects layers = [self.buildPaint(l) for l in layers] # No reason to have a colr layers with just one entry if len(layers) == 1: return layers[0], {} # Look for reuse, with preference to longer sequences # This may make the layer list smaller found_reuse = True while found_reuse: found_reuse = False ranges = sorted( _reuse_ranges(len(layers)), key=lambda t: (t[1] - t[0], t[1], t[0]), reverse=True, ) for lbound, ubound in ranges: reuse_lbound = self.reusePool.get( self._as_tuple(layers[lbound:ubound]), -1) if reuse_lbound == -1: continue new_slice = ot.Paint() new_slice.Format = int(ot.PaintFormat.PaintColrLayers) new_slice.NumLayers = ubound - lbound new_slice.FirstLayerIndex = reuse_lbound layers = layers[:lbound] + [new_slice] + layers[ubound:] found_reuse = True break # The layer list is now final; if it's too big we need to tree it is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT layers = _build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT) # We now have a tree of sequences with Paint leaves. # Convert the sequences into PaintColrLayers. def listToColrLayers(layer): if isinstance(layer, collections.abc.Sequence): return self.buildPaint({ "Format": ot.PaintFormat.PaintColrLayers, "Layers": [listToColrLayers(l) for l in layer], }) return layer layers = [listToColrLayers(l) for l in layers] # No reason to have a colr layers with just one entry if len(layers) == 1: return layers[0], {} paint = ot.Paint() paint.Format = int(ot.PaintFormat.PaintColrLayers) paint.NumLayers = len(layers) paint.FirstLayerIndex = len(self.layers) self.layers.extend(layers) # Register our parts for reuse provided we aren't a tree # If we are a tree the leaves registered for reuse and that will suffice if not is_tree: for lbound, ubound in _reuse_ranges(len(layers)): self.reusePool[self._as_tuple( layers[lbound:ubound])] = (lbound + paint.FirstLayerIndex) # we've fully built dest; empty source prevents generalized build from kicking in return paint, {}