760 lines
23 KiB
JavaScript
760 lines
23 KiB
JavaScript
/*
|
|
|
|
altium.js schematic renderer
|
|
|
|
Copyright (c) 2022 Graham Sutherland
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
a copy of this software and associated documentation files (the
|
|
"Software"), to deal in the Software without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
class AltiumSchematicRenderer
|
|
{
|
|
constructor(canvas, document)
|
|
{
|
|
this.canvas = canvas;
|
|
this.document = document;
|
|
}
|
|
|
|
#altiumColourToHex(colourInt)
|
|
{
|
|
return "#" + (colourInt & 0xFF).toString(16).padStart(2, '0') + ((colourInt >> 8) & 0xFF).toString(16).padStart(2, '0') + ((colourInt >> 16) & 0xFF).toString(16).padStart(2, '0');
|
|
}
|
|
|
|
#shouldShow(object)
|
|
{
|
|
if (object.owner_part_id == null || object.owner_part_id < 1)
|
|
return true;
|
|
|
|
const parent = object.find_parent(AltiumComponent);
|
|
if (parent == null)
|
|
return true;
|
|
|
|
if (parent.current_part_id == null || parent.current_part_id < 1)
|
|
return true;
|
|
|
|
return parent.current_part_id == object.owner_part_id;
|
|
}
|
|
|
|
render()
|
|
{
|
|
let canvas = this.canvas;
|
|
let doc = this.document;
|
|
|
|
let sheetObject = doc.objects.find(o => o instanceof AltiumSheet);
|
|
|
|
canvas.style.width = sheetObject.width + "px";
|
|
canvas.style.height = sheetObject.height + "px";
|
|
canvas.width = sheetObject.width * window.devicePixelRatio;
|
|
canvas.height = sheetObject.height * window.devicePixelRatio;
|
|
let areaColourInt = Number.parseInt(sheetObject.attributes.areacolor, 10);
|
|
let areaColour = this.#altiumColourToHex(areaColourInt);
|
|
canvas.style.backgroundColor = areaColour;
|
|
let ctx = canvas.getContext('2d');
|
|
ctx.scale(1, -1);
|
|
ctx.translate(0.5, 0.5);
|
|
ctx.translate(0, -canvas.height);
|
|
ctx.font = "7pt sans-serif";
|
|
ctx.textRendering = "optimizeLegibility";
|
|
ctx.imageSmoothingQuality = "high";
|
|
ctx.textBaseline = "bottom";
|
|
ctx.fillStyle = areaColour;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
let results = document.getElementById("results");
|
|
|
|
let sheet = doc.objects.find((o) => o instanceof AltiumSheet);
|
|
let gridLight = "#eeeeee";
|
|
let gridDark = "#cccccc";
|
|
ctx.lineWidth = 1;
|
|
ctx.globalAlpha = 0.5;
|
|
if (sheet.show_grid)
|
|
{
|
|
let n = 0;
|
|
for (let x = 0; x < canvas.width; x += sheet.grid_size)
|
|
{
|
|
ctx.strokeStyle = ((n % 10) == 0) ? gridDark : gridLight;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, 0);
|
|
ctx.lineTo(x, canvas.height);
|
|
ctx.stroke();
|
|
n++;
|
|
}
|
|
n = 0;
|
|
for (let y = 0; y < canvas.height; y += sheet.grid_size)
|
|
{
|
|
ctx.strokeStyle = ((n % 10) == 0) ? gridDark : gridLight;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y);
|
|
ctx.lineTo(canvas.width, y);
|
|
ctx.stroke();
|
|
n++;
|
|
}
|
|
}
|
|
ctx.globalAlpha = 1;
|
|
|
|
|
|
/*
|
|
|
|
ctx.textAlign = "center";
|
|
ctx.font = "bold 100px serif";
|
|
ctx.fillStyle = "#333300";
|
|
ctx.globalAlpha = 0.03;
|
|
ctx.save();
|
|
|
|
ctx.rotate(Math.PI/4);
|
|
ctx.scale(1,-1);
|
|
for (let y = 0; y < canvas.height * 2; y += 400)
|
|
{
|
|
ctx.fillText("PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA", canvas.width / 2, canvas.height - (y + 200));
|
|
ctx.fillText("BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW - BETA - PREVIEW", canvas.width / 2, canvas.height - y);
|
|
}
|
|
|
|
ctx.textAlign = "left";
|
|
*/
|
|
|
|
let bom = [];
|
|
bom.push("\"designator\", \"part\", \"description\"");
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumDesignator))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
let bomLine = "";
|
|
//let designator = doc.objects.find((des) => des instanceof AltiumDesignator && des.owner_part_id == obj.current_part_id);
|
|
let component = doc.object_from_record_index(obj.owner_record_index);
|
|
if (component != null && component instanceof AltiumComponent)
|
|
{
|
|
bomLine += "\"" + obj.text + "\", \"" + component.design_item_id + "\", \"" + component.description.replaceAll("\"", "'") + "\"";
|
|
bom.push(bomLine);
|
|
}
|
|
//bomLine += obj.description;
|
|
|
|
}
|
|
results.innerText = bom.join("\n");
|
|
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumWire))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.lineWidth = obj.width;
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.points[0].x, obj.points[0].y);
|
|
for (let i = 1; i < obj.points.length; i++)
|
|
{
|
|
ctx.lineTo(obj.points[i].x, obj.points[i].y);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumRectangle))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (!obj.transparent)
|
|
{
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.fill_colour);
|
|
ctx.fillRect(obj.left, obj.top, obj.right - obj.left, obj.bottom - obj.top);
|
|
}
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.line_colour);
|
|
ctx.strokeRect(obj.left, obj.top, obj.right - obj.left, obj.bottom - obj.top);
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumTextFrame))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (!obj.transparent)
|
|
{
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.fill_colour);
|
|
ctx.fillRect(obj.left, obj.top, obj.right - obj.left, obj.bottom - obj.top);
|
|
}
|
|
if (obj.show_border)
|
|
{
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.border_colour);
|
|
ctx.strokeRect(obj.left, obj.top, obj.right - obj.left, obj.bottom - obj.top);
|
|
}
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumEllipse))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (!obj.transparent)
|
|
{
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.fill_colour);
|
|
}
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.line_colour);
|
|
ctx.beginPath();
|
|
ctx.ellipse(obj.x, obj.y, obj.radius_x, obj.radius_y, 0, 0, Math.PI*2);
|
|
ctx.stroke();
|
|
if (!obj.transparent)
|
|
ctx.fill();
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumPin))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = "#000000";
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x, obj.y);
|
|
ctx.lineTo(obj.x + obj.angle_vec[0] * obj.length, obj.y + obj.angle_vec[1] * obj.length);
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumLine))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x1, obj.y1);
|
|
ctx.lineTo(obj.x2, obj.y2);
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumArc))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.lineWidth = obj.width;
|
|
ctx.beginPath();
|
|
ctx.arc(obj.x, obj.y, obj.radius, obj.start_angle * Math.PI/180, obj.end_angle * Math.PI/180);
|
|
ctx.stroke();
|
|
ctx.lineWidth = 1;
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumPolyline))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.lineWidth = obj.width;
|
|
|
|
switch (obj.line_style)
|
|
{
|
|
case 1:
|
|
ctx.setLineDash([4, 4]);
|
|
break;
|
|
case 2:
|
|
ctx.setLineDash([2, 2]);
|
|
break;
|
|
case 3:
|
|
ctx.setLineDash([4, 2, 2, 4]);
|
|
break;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.points[0].x, obj.points[0].y);
|
|
for (let i = 1; i < obj.points.length; i++)
|
|
{
|
|
ctx.lineTo(obj.points[i].x, obj.points[i].y);
|
|
}
|
|
ctx.stroke();
|
|
|
|
ctx.setLineDash([]);
|
|
|
|
let pa = null;
|
|
let pb = null;
|
|
let shapeSize = obj.shape_size + 1;
|
|
ctx.lineWidth = shapeSize;
|
|
if (obj.start_shape > 0)
|
|
{
|
|
let pa = obj.points[1];
|
|
let pb = obj.points[0];
|
|
let dx = pb.x - pa.x;
|
|
let dy = pb.y - pa.y;
|
|
let angle = Math.atan2(dy, dx);
|
|
const baseSize = 3 + shapeSize;
|
|
let tax = pb.x - Math.cos(angle - Math.PI/6) * baseSize;
|
|
let tay = pb.y - Math.sin(angle - Math.PI/6) * baseSize;
|
|
let tbx = pb.x - Math.cos(angle + Math.PI/6) * baseSize;
|
|
let tby = pb.y - Math.sin(angle + Math.PI/6) * baseSize;
|
|
ctx.beginPath();
|
|
ctx.moveTo(tax, tay);
|
|
ctx.lineTo(pb.x + Math.cos(angle) * 0.5, pb.y + Math.sin(angle) * 0.5);
|
|
ctx.lineTo(tbx, tby);
|
|
ctx.stroke();
|
|
if (obj.start_shape == 2 || obj.start_shape == 4)
|
|
ctx.fill();
|
|
}
|
|
if (obj.end_shape > 0)
|
|
{
|
|
let pa = obj.points[obj.points.length - 2];
|
|
let pb = obj.points[obj.points.length - 1];
|
|
let dx = pb.x - pa.x;
|
|
let dy = pb.y - pa.y;
|
|
let angle = Math.atan2(dy, dx);
|
|
const baseSize = 3 + shapeSize;
|
|
let tax = pb.x - Math.cos(angle - Math.PI/6) * baseSize;
|
|
let tay = pb.y - Math.sin(angle - Math.PI/6) * baseSize;
|
|
let tbx = pb.x - Math.cos(angle + Math.PI/6) * baseSize;
|
|
let tby = pb.y - Math.sin(angle + Math.PI/6) * baseSize;
|
|
ctx.beginPath();
|
|
ctx.moveTo(tax, tay);
|
|
ctx.lineTo(pb.x + Math.cos(angle) * 0.5, pb.y + Math.sin(angle) * 0.5);
|
|
ctx.lineTo(tbx, tby);
|
|
ctx.stroke();
|
|
if (obj.end_shape == 2 || obj.end_shape == 4)
|
|
ctx.fill();
|
|
}
|
|
ctx.lineWidth = 1;
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumPolygon))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.line_colour);
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.fill_colour);
|
|
ctx.lineWidth = obj.width;
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.points[0].x, obj.points[0].y);
|
|
for (let i = 1; i < obj.points.length; i++)
|
|
{
|
|
ctx.lineTo(obj.points[i].x, obj.points[i].y);
|
|
}
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
ctx.fill();
|
|
ctx.lineWidth = 1;
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumJunction))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.beginPath();
|
|
ctx.ellipse(obj.x, obj.y, 2, 2, 0, 0, 2*Math.PI);
|
|
ctx.fill();
|
|
}
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumPowerPort))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
ctx.strokeStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.lineWidth = 1;
|
|
if (!obj.is_off_sheet_connector)
|
|
{
|
|
switch (obj.style)
|
|
{
|
|
case 2:
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x, obj.y);
|
|
ctx.lineTo(obj.x, obj.y + 10);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x - 5, obj.y + 10);
|
|
ctx.lineTo(obj.x + 5, obj.y + 10);
|
|
ctx.stroke();
|
|
break;
|
|
case 4:
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x - 10, obj.y);
|
|
ctx.lineTo(obj.x + 10, obj.y);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x - 7.5, obj.y - 2);
|
|
ctx.lineTo(obj.x + 7.5, obj.y - 2);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x - 5, obj.y - 4);
|
|
ctx.lineTo(obj.x + 5, obj.y - 4);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x - 2.5, obj.y - 6);
|
|
ctx.lineTo(obj.x + 2.5, obj.y - 6);
|
|
ctx.stroke();
|
|
break;
|
|
case 6:
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x, obj.y);
|
|
ctx.lineTo(obj.x, obj.y - 5);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x - 5, obj.y - 5);
|
|
ctx.lineTo(obj.x + 5, obj.y - 5);
|
|
ctx.stroke();
|
|
for (let g = -1; g < 2; g++)
|
|
{
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x + (g * 5), obj.y - 5);
|
|
ctx.lineTo(obj.x + (g * 5) - 3, obj.y - 10);
|
|
ctx.stroke();
|
|
}
|
|
break;
|
|
default:
|
|
ctx.fillRect(obj.x - 10, obj.y, 20, (obj.orientation == 1) ? 10 : -10);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x, obj.y);
|
|
ctx.rotate((obj.orientation - 1) * Math.PI/2);
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(-5, 5);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
ctx.lineTo(5, 5);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 5);
|
|
ctx.lineTo(-5, 10);
|
|
ctx.stroke();
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 5);
|
|
ctx.lineTo(5, 10);
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
//ctx.fillText(obj.style.toString(), obj.x, obj.y);
|
|
}
|
|
|
|
// store the transform for recovery later
|
|
let savedTransform = ctx.getTransform();
|
|
ctx.resetTransform();
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumLabel))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (obj.hidden)
|
|
continue;
|
|
ctx.textAlign = ["left", "center", "right"][obj.justification];
|
|
ctx.textBaseline = ["bottom", "bottom", "top", "top"][obj.orientation];
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.save();
|
|
ctx.translate(obj.x, canvas.height - obj.y);
|
|
ctx.rotate(obj.orientation * -Math.PI/2);
|
|
ctx.fillText(obj.text, 0, 0);
|
|
ctx.restore();
|
|
}
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumDesignator))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (obj.hidden)
|
|
continue;
|
|
ctx.textAlign = ["left", "left", "right", "right"][obj.orientation];
|
|
ctx.textBaseline = ["bottom", "bottom", "top", "top"][obj.orientation];
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.fillText(obj.full_designator, obj.x, canvas.height - obj.y);
|
|
}
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumParameter))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (obj.hidden || obj.is_implementation_parameter)
|
|
continue;
|
|
|
|
ctx.textAlign = ["left", "left", "right", "right"][obj.orientation];
|
|
ctx.textBaseline = ["bottom", "bottom", "top", "top"][obj.orientation];
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
if (obj.orientation == 1)
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x, canvas.height - obj.y);
|
|
ctx.rotate(-Math.PI/2);
|
|
ctx.fillText(obj.text, 0, 0);
|
|
ctx.restore();
|
|
}
|
|
else if (obj.orientation == 3)
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x, canvas.height - obj.y);
|
|
ctx.rotate(Math.PI/2);
|
|
ctx.fillText(obj.text, 0, 0);
|
|
ctx.restore();
|
|
}
|
|
else
|
|
{
|
|
ctx.fillText(obj.text, obj.x, canvas.height - obj.y);
|
|
}
|
|
}
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumNetLabel))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (obj.hidden)
|
|
continue;
|
|
ctx.textAlign = ["left", "center", "right"][obj.justification];
|
|
ctx.textBaseline = ["bottom", "bottom", "top", "top"][obj.orientation];
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
if (obj.orientation == 1)
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x, canvas.height - obj.y);
|
|
ctx.rotate(-Math.PI/2);
|
|
ctx.fillText(obj.text, 0, 0);
|
|
ctx.restore();
|
|
}
|
|
else if (obj.orientation == 3)
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x, canvas.height - obj.y);
|
|
ctx.rotate(Math.PI/2);
|
|
ctx.fillText(obj.text, 0, 0);
|
|
ctx.restore();
|
|
}
|
|
else
|
|
{
|
|
ctx.fillText(obj.text, obj.x, canvas.height - obj.y);
|
|
}
|
|
}
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumPin))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (!obj.show_name)
|
|
continue;
|
|
ctx.textAlign = ["right", "right", "left", "right"][obj.orientation];
|
|
ctx.textBaseline = "middle";
|
|
let objName = obj.name;
|
|
let inverted = false;
|
|
if (obj.name.includes("\\"))
|
|
{
|
|
objName = obj.name.replaceAll("\\", "");
|
|
inverted = true;
|
|
}
|
|
if (obj.name_orientation != 0)
|
|
{
|
|
ctx.textBaseline = ["middle", "top", "middle", "bottom"][obj.orientation];
|
|
if (obj.name_orientation <= 3)
|
|
ctx.textAlign = ["left", "center", "right"][obj.name_orientation-1];
|
|
else
|
|
ctx.textAlign = "center";
|
|
}
|
|
let margin_x = [-1, 0, 1, 0][obj.orientation] * 2;
|
|
let margin_y = [0, -1, 0, 1][obj.orientation] * 2;
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.strokeStyle = ctx.fillStyle;
|
|
ctx.lineWidth = 1;
|
|
if (obj.orientation == 1 && obj.name_orientation == 0)
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x + margin_x, canvas.height - (obj.y + margin_y));
|
|
ctx.rotate(-Math.PI/2);
|
|
ctx.fillText(objName, 0, 0);
|
|
if (inverted)
|
|
{
|
|
// todo: test this
|
|
let textSize = ctx.measureText(objName);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, textSize.actualBoundingBoxAscent + 2);
|
|
ctx.lineTo(textSize.width, textSize.actualBoundingBoxAscent + 2);
|
|
ctx.stroke();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
else if (obj.orientation == 3 && obj.name_orientation == 0)
|
|
{
|
|
ctx.save();
|
|
ctx.translate(obj.x + margin_x, canvas.height - (obj.y + margin_y));
|
|
ctx.rotate(Math.PI/2);
|
|
ctx.fillText(objName, 0, 0);
|
|
if (inverted)
|
|
{
|
|
// todo: test this
|
|
let textSize = ctx.measureText(objName);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, textSize.actualBoundingBoxAscent + 2);
|
|
ctx.lineTo(textSize.width, textSize.actualBoundingBoxAscent + 2);
|
|
ctx.stroke();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
else
|
|
{
|
|
ctx.fillText(objName, obj.x + margin_x, canvas.height - (obj.y + margin_y));
|
|
if (inverted)
|
|
{
|
|
let textSize = ctx.measureText(objName);
|
|
let offset = 0;
|
|
switch (ctx.textAlign)
|
|
{
|
|
case "center":
|
|
offset = -(textSize.width/2);
|
|
break;
|
|
case "right":
|
|
offset = -textSize.width;
|
|
break;
|
|
case "left":
|
|
offset = 0;
|
|
break;
|
|
default:
|
|
offset = 0;
|
|
break;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo(obj.x + margin_x + offset, canvas.height - (obj.y + margin_y + textSize.actualBoundingBoxAscent + 2));
|
|
ctx.lineTo(obj.x + margin_x + offset + textSize.width, canvas.height - (obj.y + margin_y + textSize.actualBoundingBoxAscent + 2));
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
ctx.setLineDash([]);
|
|
}
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumPowerPort))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (!obj.show_text)
|
|
continue;
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.colour);
|
|
ctx.textBaseline = ["middle", "top", "middle", "bottom"][obj.orientation];
|
|
ctx.textAlign = ["left", "center", "right", "center"][obj.orientation];
|
|
let offset_x = [12, 0, -12, 0][obj.orientation];
|
|
let offset_y = [0, 20, 0, -20][obj.orientation];
|
|
ctx.fillText(obj.text, obj.x + offset_x, canvas.height - (obj.y + offset_y));
|
|
}
|
|
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "middle";
|
|
let savedFont = ctx.font;
|
|
for (let obj of doc.objects.filter((o) => o instanceof AltiumTextFrame))
|
|
{
|
|
if (!this.#shouldShow(obj)) continue;
|
|
|
|
if (obj.font_id > 0 && doc.sheet.fonts[obj.font_id] != null)
|
|
{
|
|
const frameFont = doc.sheet.fonts[obj.font_id];
|
|
const fontStr = (frameFont.size - 1).toString() + "px " + frameFont.name;
|
|
if (fontStr.includes(":") || fontStr.includes("/") || !document.fonts.check(fontStr))
|
|
{
|
|
ctx.font = savedFont;
|
|
}
|
|
else
|
|
{
|
|
ctx.font = fontStr;
|
|
}
|
|
}
|
|
|
|
ctx.fillStyle = this.#altiumColourToHex(obj.text_colour);
|
|
ctx.textAlign = ["center", "left", "right"][obj.alignment];
|
|
let offset_x = [(obj.right-obj.left)/2, obj.text_margin, (obj.right-obj.left) - obj.text_margin][obj.alignment];
|
|
if (!obj.word_wrap)
|
|
{
|
|
ctx.fillText(obj.text.replaceAll("~1", "\n"), obj.left + offset_x, canvas.height - (obj.top + (obj.bottom-obj.top)/2));
|
|
}
|
|
else
|
|
{
|
|
// todo: refactor this so that an initial pass figures out all the line splits, then a second pass writes the text, so that vertical alignment can be supported.
|
|
const text = obj.text.replaceAll("~1", "\n");
|
|
const lines = text.split("\n");
|
|
let ypos = 0;
|
|
if (lines.length > 1)
|
|
{
|
|
// this is a total hack, but if there are multiple lines in the text then we can make a rough guess at how far up we need to shift the text to center it vertically
|
|
// this doesn't correct for line wraps (see todo above for refactoring approach) but it's at least something I guess!
|
|
const roughMeasure = ctx.measureText(text);
|
|
ypos = ((roughMeasure.fontBoundingBoxDescent + roughMeasure.fontBoundingBoxAscent) * -lines.length) / 2;
|
|
}
|
|
const maxWidth = (obj.right - obj.left) + (obj.text_margin * 2);
|
|
for (let line of lines)
|
|
{
|
|
const lineMeasure = ctx.measureText(line);
|
|
if (lineMeasure.width <= maxWidth)
|
|
{
|
|
ctx.fillText(line, obj.left + offset_x, (canvas.height - (obj.top + (obj.bottom-obj.top)/2)) + ypos);
|
|
ypos += lineMeasure.fontBoundingBoxDescent + lineMeasure.fontBoundingBoxAscent;
|
|
}
|
|
else
|
|
{
|
|
let words = line.split(" ");
|
|
while (words.length > 0)
|
|
{
|
|
if (words.length == 1)
|
|
{
|
|
// we only have one word, either because that's just how many we had or because the final word is super long
|
|
const lastWord = words[0];
|
|
const lastWordMeasure = ctx.measureText(lastWord);
|
|
ctx.fillText(lastWord, obj.left + offset_x, (canvas.height - (obj.top + (obj.bottom-obj.top)/2)) + ypos);
|
|
ypos += lastWordMeasure.fontBoundingBoxDescent + lineMeasure.fontBoundingBoxAscent;
|
|
words = [];
|
|
break;
|
|
}
|
|
for (let wc = words.length; wc > 0; wc--)
|
|
{
|
|
const partialLine = words.slice(0, wc - 1).join(" ");
|
|
const partialMeasure = ctx.measureText(partialLine);
|
|
if (partialMeasure.width <= maxWidth || wc == 1)
|
|
{
|
|
ctx.fillText(partialLine, obj.left + offset_x, (canvas.height - (obj.top + (obj.bottom-obj.top)/2)) + ypos);
|
|
ypos += partialMeasure.fontBoundingBoxDescent + lineMeasure.fontBoundingBoxAscent;
|
|
words = words.slice(wc - 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ctx.font = savedFont;
|
|
|
|
ctx.textAlign = "left";
|
|
ctx.textBaseline = "bottom";
|
|
|
|
ctx.setTransform(savedTransform);
|
|
|
|
savedFont = ctx.font;
|
|
ctx.textAlign = "left";
|
|
ctx.font = "bold 33px sans-serif";
|
|
ctx.fillStyle = "#000000";
|
|
ctx.globalAlpha = 0.2;
|
|
ctx.save();
|
|
ctx.scale(1,-1);
|
|
ctx.fillText("Preview generated by altium.js", 10, -(canvas.height - 50));
|
|
ctx.font = "bold 15px sans-serif";
|
|
ctx.fillText("for reference purposes only. schematic accuracy not guaranteed.", 12, -(canvas.height - 75));
|
|
ctx.restore();
|
|
ctx.globalAlpha = 1;
|
|
ctx.font = savedFont;
|
|
}
|
|
} |